﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using InterLinq.Base;
using InterLinq.Services.Interface;
using InterLinq.Services.Interfaces;
using Sebarf.Core.Interfaces;
using Sebarf.Diagnostics.Interfaces;
using Sebastof.Services.Interfaces;
using Sebastof.Types;
using Utils;

#endregion

namespace Sebastof.Server.Services.Impl {
	/// <summary>
	/// This is the request handler for the SubmitChanges requests.
	/// All requests are processed by 
	/// 1) updating the local cache and then 
	/// 2) wrap the changes into change sets understood by the back end providers and then
	/// 3) call the back end provider to comit the changes.
	/// </summary>
	public class SubmitChangesRequestHandler : RequestHandler<SubmitChangesRequest> {
		#region Public Methods

		/// <summary>
		/// Processess the given submit changes request
		/// </summary>
		/// <param name="request"></param>
		/// <param name="serviceLocator"></param>
		/// <returns></returns>
		protected override object OnProcessRequest(SubmitChangesRequest request, IServiceLocatorService serviceLocator) {
			Logger.WriteDebug("SubmitChangesRequestHandler.OnProcessRequest (enter)");

			var listWithNewVersionOfObjects = new List<KeyValuePair<string, object>>();
			IBackendProvider backEndProvider =
				serviceLocator.GetRegisteredService<IBackendProviderLocatorService>().GetBackendProvider();
			lock (this) {
				object t = backEndProvider.BeginTransaction();
				try {
					CheckVersion(request.GeneralChanges, serviceLocator);

					if (request.GeneralChanges != null) {
						SaveChanges(request.GeneralChanges, serviceLocator);
					}
					if (request.ChangesOnCollection != null) {
						SaveChanges(request.ChangesOnCollection, serviceLocator);
					}
					if (request.ChangesByCreationOfObjects != null) {
						SaveChanges(request.ChangesByCreationOfObjects, serviceLocator);
					}
					if (request.ChangesByDeletionOfObjects != null) {
						SaveChanges(request.ChangesByDeletionOfObjects, serviceLocator);
					}
					backEndProvider.CommitTransaction(t);
				}
				catch (Exception) {
					/* //TODO: check if here is a rollback needed. 
					 * normally one would say yes, but we have only pseudo transaction, so rollback is not possible anyway. 
					 * watch this!
					 */
					backEndProvider.CommitTransaction(t);
					throw;
				}
				var m = serviceLocator.GetRegisteredService<IObjectMappingLocatorService>();
				Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();

				foreach (ChangeSet e in request.GeneralChanges) {
					var o = ((IVersionedObject)(lookup[e.oid.ToString()].Target));
					o.IncreaseVersion();
					listWithNewVersionOfObjects.Add(new KeyValuePair<string, object>(e.oid.ToString(), o.GetVersion()));
				}
				foreach (ChangeCollectionSet e in request.ChangesOnCollection) {
					var o = (IVersionedObject)(lookup[e.oidOfCollectionObject.ToString()].Target);
					o.IncreaseVersion();
					listWithNewVersionOfObjects.Add(new KeyValuePair<string, object>(e.oidOfCollectionObject.ToString(),
																					   o.GetVersion()));
				}
			}
			Logger.WriteDebug("SubmitChangesRequestHandler.OnProcessRequest (exit)");
			return listWithNewVersionOfObjects.ToArray();
		}

		#endregion

		#region Private Methods

		private void CheckVersion(IEnumerable<ChangeSet> changes, IServiceLocatorService locator) {
			var m = locator.GetRegisteredService<IObjectMappingLocatorService>();

			Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();
			foreach (ChangeSet change in changes) {
				//if we have the object loaded, use the loaded one.. else load the needed object from data source
				object o = null;
				if (lookup.ContainsKey(change.oid.ToString())) {
					if (lookup[change.oid.ToString()].IsAlive) {
						o = lookup[change.oid.ToString()].Target;
					}
				}
				if (o == null && change.otype != null) {
					//load o
					o =
						(from IIdentifiable ol in
							 locator.GetRegisteredService<IQueryHandlerLocatorService>().GetQueryHandler().GetTable(change.otype).
							 AsQueryable()
						 where ol.Id.Equals(change.oid)
						 select ol).Single();
					lookup.Add(change.oid.ToString(), new WeakReference(o));
				}
				if (o == null) {
					throw new Exception("Tried to update a non-existing object (on savechanges on server side)");
				}

				if (!((IVersionedObject)o).CurrentVersionMatchs(change.version)) {
					throw new Exception("Version Conflict! Can not update object.");
				}
			}
		}

		/// <summary>
		/// Save changes method for saving change to objects
		/// </summary>
		/// <param name="changes"></param>
		/// <param name="locator"></param>
		private void SaveChanges(IEnumerable<ChangeSet> changes, IServiceLocatorService locator) {
			var changedObjects = new List<BackendProviderChangeSet>();

			var m = locator.GetRegisteredService<IObjectMappingLocatorService>();
			var changeApplierService = locator.GetRegisteredService<IChangeApplierService>();

			Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();
			foreach (ChangeSet change in changes) {
				//if we have the object loaded, use the loaded one.. else load the needed object from data source
				object o = null;
				if (lookup.ContainsKey(change.oid.ToString())) {
					if (lookup[change.oid.ToString()].IsAlive) {
						o = lookup[change.oid.ToString()].Target;
					}
				}
				if (o == null && change.otype != null) {
					//load o
					o =
						(from IIdentifiable ol in
							 locator.GetRegisteredService<IQueryHandlerLocatorService>().GetQueryHandler().GetTable(change.otype).
							 AsQueryable()
						 where ol.Id.Equals(change.oid)
						 select ol).Single();
				}
				if (o == null) {
					throw new Exception("Tried to update a non-existing object (on savechanges on server side)");
				}
				//TODO: Check, was conflicted					
				changeApplierService.SetValue(o, change.field, change.newvalue);

				changedObjects.Add(new BackendProviderChangeSet { obj = o, field = change.field, newvalue = change.newvalue });
			}

			locator.GetRegisteredService<IBackendProviderLocatorService>().GetBackendProvider().SaveChanges(changedObjects);
		}

		/// <summary>
		/// Save changes method for saving changes made to collections of objects
		/// </summary>
		/// <param name="changes"></param>
		/// <param name="locator"></param>
		private void SaveChanges(IEnumerable<ChangeCollectionSet> changes, IServiceLocatorService locator) {
			var changedObjects = new List<BackendProviderChangeSet>();

			var m = locator.GetRegisteredService<IObjectMappingLocatorService>();
			var mRegister = locator.GetRegisteredService<IObjectMappingRegisterService>();
			Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();
			foreach (ChangeCollectionSet change in changes) {
				if (!lookup.ContainsKey(change.oidOfCollectionObject.ToString())) {
					throw new Exception("Tried to update a non-existing object (on savechanges on server side)");
				}

				//TODO changedObjects.Add(new BackendProviderChangeSet { obj = o, field = change.field, newvalue = change.newvalue });
				var objToChange = lookup[change.oidOfCollectionObject.ToString()].Target as SebastofApplicationObject;
				switch (change.operation) {
					case ECollectionChangeOperation.Insert:
						(objToChange.GetValue(change.fieldContainingCollection) as IList).Add(change.newvalue);
						mRegister.AddMapping((change.newvalue as IIdentifiable).Id, change.newvalue);
						break;
					case ECollectionChangeOperation.RemoveAt:
						(objToChange.GetValue(change.fieldContainingCollection) as IList).RemoveAt(change.indexToOperateOn);
						break;
					default:
						throw new NotImplementedException("Submit Change Change Set Type not implemented");
				}
			}

			locator.GetRegisteredService<IBackendProviderLocatorService>().GetBackendProvider().SaveChanges(changedObjects);
		}

		/// <summary>
		/// Save changes method for not saving changes but saving newly created objects
		/// </summary>
		/// <param name="changes"></param>
		/// <param name="locator"></param>
		private void SaveChanges(IEnumerable<NewObjectSet> changes, IServiceLocatorService locator) {
			BindingFlags TraceFields = BindingFlags.FlattenHierarchy | BindingFlags.Instance | BindingFlags.Public |
									   BindingFlags.NonPublic;

			var changedObjects = new List<BackendProviderNewSet>();

			var m = locator.GetRegisteredService<IObjectMappingLocatorService>();
			Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();
			foreach (NewObjectSet change in changes) {
				//TODO: use change applier service?
				var newO = Activator.CreateInstance(change.type) as SebastofApplicationObject;
				newO.Id = change.oid;
				lookup.Add(newO.Id, new WeakReference(newO));

				foreach (var fieldvalue in change.normalcontent) {
					FieldInfo field = newO.GetType().GetField(fieldvalue.Key, TraceFields);
					if (field != null) {
						field.SetValue(newO, fieldvalue.Value);
					}
					else {
						PropertyInfo prop = newO.GetType().GetProperty(fieldvalue.Key, TraceFields);
						prop.SetValue(newO, fieldvalue.Value, null);
					}
				}

				foreach (var fieldvalue in change.interlinqcontent) {
					newO.SetValue(fieldvalue.Key, fieldvalue.Value);
				}
				changedObjects.Add(new BackendProviderNewSet { obj = newO, type = newO.GetType() });
			}

			locator.GetRegisteredService<IBackendProviderLocatorService>().GetBackendProvider().SaveChanges(changedObjects);
		}

		/// <summary>
		/// Save changes method which deletes deleted objects.
		/// </summary>
		/// <param name="changes"></param>
		/// <param name="locator"></param>
		private void SaveChanges(IEnumerable<DeleteObjectSet> changes, IServiceLocatorService locator) {
			var changedObjects = new List<BackendProviderDeleteSet>();

			var m = locator.GetRegisteredService<IObjectMappingLocatorService>();
			Dictionary<string, WeakReference> lookup = m.GetIdToObjectMappingTable();
			foreach (DeleteObjectSet change in changes) {
				WeakReference realObj = lookup[change.oid];
				changedObjects.Add(new BackendProviderDeleteSet { type = realObj.Target.GetType(), obj = realObj.Target });
			}

			locator.GetRegisteredService<IBackendProviderLocatorService>().GetBackendProvider().SaveChanges(changedObjects);
		}

		#endregion
	}
}